// CodeMirror, copyright (c) by Marijn Haverbeke and others
// Distributed under an MIT license: https://codemirror.net/LICENSE

//mytatis提示定义
function mybatisHintHandler(CodeMirror) {
  var Pos = CodeMirror.Pos;

  var childrens = ["foreach","if","choose", "where","include","trim","set","bind"];
  var jdbcTypes = ["ARRAY","BIGINT","BINARY","BIT","BLOB","BOOLEAN","CHAR","CLOB","CURSOR","DATE"
      ,"DECIMAL","DOUBLE","FLOAT","INTEGER","LONGVARBINARY","LONGVARCHAR","NUMERIC","NCHAR","NCLOB"
      ,"NULL","NVARCHAR","OTHER","REAL","SMALLINT","STRUCT","TIME","TIMESTAMP","TINYINT","UNDEFINED","VARBINARY","VARCHAR"];
  var typeHandlerStr = "LocalDateTimeTypeHandler,ZonedDateTimeTypeHandler,OffsetDateTimeTypeHandler,LocalDateTypeHandler,lnstantTypeHandler,OffsetTimeTypeHandler,YearTypeHandler,MonthTypeHandler,LocalTimeTypeHandler,JapaneseDateTypeHandler,YearMonthTypeHandler,BlobTypeHandler,SqlTimestampTypeHandler,BigDecimalTypeHandler,BloblnputStreamTypeHandler,ByteTypeHandler,DateOnlyTypeHandler,EnumOrdinalTypeHandler,NClobTypeHandler,ShortTypeHandler,EnumTypeHandler,LongTypeHandler,ClobTypeHandler,ByteObjectArrayTypeHandler,ArrayTypeHandler,BlobByteObjectArrayTypeHandler,sqITimeTypeHandler,BigIntegerTypeHandler,ByteArrayTypeHandler,BooleanTypeHandler,ClobReaderTypeHandler,objectTypeHandler,UnknownTypeHandler,DoubleTypeHandler,DateTypeHandler,CharacterTypeHandler,FloatTypeHandler,NStringTypeHandler,StringTypeHandler,TimeOnlyTypeHandler,SqlDateTypeHandler,lntegerTypeHandler";
  var typeHandlers = typeHandlerKeys("org.apache.ibatis.type",typeHandlerStr.split(","));
  var s = { attrs: {} }; // Simple tag, reused for a whole lot of tags
  var topParentTag = "mapper";
  var data = {
    "mapper": {
                attrs: {
                    namespace: [""]
                },
                children: ["select","update","delete", "insert","sql","parameterMap","resultMap","cache","cache-ref"]
    },
    "select": {
        attrs: {
            id: [""], parameterType:["","java.util.Map"], parameterMap:[""], resultType:["","java.util.Map"], resultMap:[""]
        },
        children: childrens
    },
    "update": {
        attrs: {
            id: [""], parameterType:["","java.util.Map"], parameterMap:[""]
        },
        children: childrens
    },
    "delete": {
        attrs: {
            id: [""], parameterType:["","java.util.Map"], parameterMap:[""]
        },
        children: childrens
    },
    "insert": {
        attrs: {
            id: [""], parameterType:["","java.util.Map"], parameterMap:[""], keyColumn:[""], keyProperty:[""], useGeneratedKeys:["true"]
        },
        children: childrens
    },
    "if": {
        attrs: {
            test:[""]
        },
        children: childrens
    },
    "where": {
        attrs: {
        },
        children: childrens
    },
    "choose": {
        attrs: {
        },
        children: ["when","otherwise"]
    },
    "when": {
        attrs: {
          test: [""]
        },
        children: childrens
    },
    "otherwise": {
        attrs: {
        },
        children: childrens
    },
    "include": {
        attrs: {
            refid: [""]
        },
        children: ["property"]
    },
    "trim": {
        attrs: {
          prefix:[""], suffix:[""], suffixOverrides:[""], prefixOverrides:[""]
        },
        children: childrens
    },
    "set": {
        attrs: {
        },
        children: childrens
    },
    "sql": {
        attrs: {
          id: [""]
        },
        children: childrens
    },
    "foreach": {
        attrs: {
            collection:[""], item:[""], index:[""], separator:[""],close:[""], open:[""]
        },
        children: childrens
    },
    "parameterMap": {
        attrs: {
          id: [""], type:[""]
        },
        children:["parameter"]
    },
    "parameter": {
        attrs: {
            property:[""], javaType:[""], jdbcType:jdbcTypes, mode:["IN","OUT","INOUT"], resultMap:[""], scale:[""], typeHandler:typeHandlers
        }
    },
    "resultMap": {
        attrs: {
          id: [""], type:[""], "extends":[""],autoMapping:["true","false"]
        },
        children:["id","result","association","collection","constructor","discriminator"]
    },
    "constructor": {
        attrs: {
        },
        children:["idArg","arg"]
    },
    "idArg": {
        attrs: {
          column:[""], javaType:[""], jdbcType:jdbcTypes, select:[""], typeHandler:typeHandlers
          ,resultMap:[""],name:[""], columnPrefix:[""]
        },
    },
    "arg": {
        attrs: {
          column:[""], javaType:[""], jdbcType:jdbcTypes, select:[""], typeHandler:typeHandlers
          ,resultMap:[""],name:[""], columnPrefix:[""]
        },
        children:[]
    },
    "collection": {
        attrs: {
          column:[""],property:[""], javaType:[""], jdbcType:jdbcTypes, ofType:[""], typeHandler:typeHandlers
          , select:[""],resultMap:[""],notNullColumn:[""], columnPrefix:[""],resultSet:[""]
          ,foreignColumn:[""], autoMapping:["true","false"], fetchType:["lazy","eager"]
        },
        children:["constructor","id","result","association","collection","discriminator"]
    },
    "association": {
        attrs: {
          column:[""],property:[""], javaType:[""], jdbcType:jdbcTypes, typeHandler:typeHandlers
          , select:[""],resultMap:[""],notNullColumn:[""], columnPrefix:[""],resultSet:[""]
          ,foreignColumn:[""], autoMapping:["true","false"], fetchType:["lazy","eager"]
        },
        children:["constructor","id","result","association","collection","discriminator"]
    },
    "discriminator": {
        attrs: {
          column:[""],javaType:[""], jdbcType:jdbcTypes, typeHandler:typeHandlers
        },
        children:["case"]
    },
    "case": {
        attrs: {
            value:[""],resultMap:[""], resultType:[""]
        },
        children:["constructor","id","result","association","collection","discriminator"]
    },
    "property": {
        attrs: {
            value:[""],name:[""]
        },
        children:[]
    },
    "typeAlias": {
        attrs: {
            alias:[""],type:[""]
        },
    },
    "bind": {
        attrs: {
            value:[""],name:[""]
        },
        children:[]
    },
    "id": {
        attrs: {
          column:[""], javaType:[""], jdbcType:jdbcTypes, property:[""], typeHandler:typeHandlers
        },
        children:[]
    },
    "result": {
        attrs: {
          column:[""], javaType:[""], jdbcType:jdbcTypes, property:[""], typeHandler:typeHandlers
        },
        children:[]
    },
    "cache": {
        attrs: {
          blocking:[""], eviction:[""], flushInterval:[""], readOnly:[""], size:[""], type:[""]
        },
        children:[]
    },
    "cache-ref": {
        attrs: {
            namespace:[""]
        },
        children:[]
    },
  };
  //全局属性
  var globalAttrs = {
      
  };
  function populate(obj) {
    for (var attr in globalAttrs) if (globalAttrs.hasOwnProperty(attr))
      obj.attrs[attr] = globalAttrs[attr];
  }

  populate(s);
  for (var tag in data) if (data.hasOwnProperty(tag) && data[tag] != s)
    populate(data[tag]);

  function typeHandlerKeys(packageId,types) {
    var typeHandlers = [];
    for (var i=0;i<types.length;i++) 
       typeHandlers.push(packageId+types[i]);
    return typeHandlers;
  }
  
  function matches(hint, typed, matchInMiddle) {
    if (matchInMiddle) return hint.indexOf(typed) >= 0;
    else return hint.toLowerCase().lastIndexOf(typed.toLowerCase(), 0) == 0;
  }
  
  CodeMirror.mybatisSchema = data;

  function getHints(cm, options) {
    var tags = options && options.schemaInfo;
    var quote = (options && options.quoteChar) || '"';
    var matchInMiddle = options && options.matchInMiddle;
    options.mybatis=true;
    options.isInnerAttr=false;
    if (!tags) return;
    var cur = cm.getCursor(), token = cm.getTokenAt(cur);
    if (token.end > cur.ch) {
      token.end = cur.ch;
      token.string = token.string.slice(0, cur.ch - token.start);
    }
    var inner = CodeMirror.innerMode(cm.getMode(), token.state);
    if (!inner.mode.xmlCurrentTag) return
    var result = [], replaceToken = false, prefix;
    var tag = /\btag\b/.test(token.type) && !/>$/.test(token.string);
    var tagName = tag && /^\w/.test(token.string), tagStart;

    if (tagName) {
      var before = cm.getLine(cur.line).slice(Math.max(0, token.start - 2), token.start);
      var tagType = /<\/$/.test(before) ? "close" : /<$/.test(before) ? "open" : null;
      if (tagType) tagStart = token.start - (tagType == "close" ? 2 : 1);
    } else if (tag && token.string == "<") {
      tagType = "open";
    } else if (tag && token.string == "</") {
      tagType = "close";
    }
    var tagInfo = inner.mode.xmlCurrentTag(inner.state)
    if (!tag && !tagInfo || tagType) {
      if (tagName)
        prefix = token.string;
      replaceToken = tagType;
      var context = inner.mode.xmlCurrentContext ? inner.mode.xmlCurrentContext(inner.state) : []
      var inner = context.length && context[context.length - 1]
      var curTag = inner && tags[inner]
      var childList = inner ? curTag && curTag.children : tags["!top"];
      if(tagType){
        if (childList && tagType != "close") {
          for (var i = 0; i < childList.length; ++i) if (!prefix || matches(childList[i], prefix, matchInMiddle))
            result.push("<" + childList[i]);
        } else if (tagType != "close") {
          for (var name in tags)
            if (tags.hasOwnProperty(name) && name != "!top" && name != "!attrs" && (!prefix || matches(name, prefix, matchInMiddle)))
              result.push("<" + name);
        }
        if (inner && (!prefix || tagType == "close" && matches(inner, prefix, matchInMiddle)))
          result.push("</" + inner + ">");
      }else if((!/\s+$/.test(token.string)) && !tagType && inner !==topParentTag){
        if(token.string !== "=" && CodeMirror.hint.hasOwnProperty("sqlquery")){
             return CodeMirror.hint["sqlquery"](cm, options);
        }
      }
    } else {
      // Attribute completion
      var curTag = tagInfo && tags[tagInfo.name], attrs = curTag && curTag.attrs;
      var globalAttrs = tags["!attrs"];
      if (!attrs && !globalAttrs) return;
      if (!attrs) {
        attrs = globalAttrs;
      } else if (globalAttrs) { // Combine tag-local and global attributes
        var set = {};
        for (var nm in globalAttrs) if (globalAttrs.hasOwnProperty(nm)) set[nm] = globalAttrs[nm];
        for (var nm in attrs) if (attrs.hasOwnProperty(nm)) set[nm] = attrs[nm];
        attrs = set;
      }
      if (token.type == "string" || token.string == "=") { // A value
        var before = cm.getRange(Pos(cur.line, Math.max(0, cur.ch - 60)),
                                 Pos(cur.line, token.type == "string" ? token.start : token.end));
        var atName = before.match(/([^\s\u00a0=<>\"\']+)=$/), atValues;
        if (!atName || !attrs.hasOwnProperty(atName[1]) || !(atValues = attrs[atName[1]])) return;
        if (typeof atValues == 'function') atValues = atValues.call(this, cm); // Functions can be used to supply values for autocomplete widget
        if (token.type == "string") {
          prefix = token.string;
          var n = 0;
          if (/['"]/.test(token.string.charAt(0))) {
            quote = token.string.charAt(0);
            prefix = token.string.slice(1);
            n++;
          }
          var len = token.string.length;
          if (/['"]/.test(token.string.charAt(len - 1))) {
            quote = token.string.charAt(len - 1);
            prefix = token.string.substr(n, len - 2);
          }
          if (n) { // an opening quote
            var line = cm.getLine(cur.line);
            if (line.length > token.end && line.charAt(token.end) == quote) token.end++; // include a closing quote
          }
          replaceToken = true;
        }
        var returnHintsFromAtValues = function(atValues) {
          if (atValues)
            for (var i = 0; i < atValues.length; ++i) if (!prefix || matches(atValues[i], prefix, matchInMiddle)){
              result.push(quote + atValues[i] + quote);
            }
          if(result.length===0 && CodeMirror.hint.hasOwnProperty("sqlquery")){
               options.isInnerAttr=true;
               var sql = CodeMirror.hint["sqlquery"](cm, options);
               return sql;
          }
          return returnHints();
        };
        if (atValues && atValues.then) return atValues.then(returnHintsFromAtValues);
        return returnHintsFromAtValues(atValues);
      } else { // An attribute name
        if (token.type == "attribute") {
          prefix = token.string;
          replaceToken = true;
        }
        var hasChild =[];
        if(!prefix){
          var t=cur.ch+1, tend=cm.getRange(cur,CodeMirror.Pos(cur.line, t), "\n");
          var cln=cm.getLine(cur.line).length;
          var r1=new RegExp("\\s*\/*(?<!\\\\)>$|\\s*((?<!\\\\)<)$"), r2=new RegExp("\\b[a-z]+(-[a-z]+)*\\b","ig");
          while(t< cln&& !r1.test(tend)){
              t++;
              tend = cm.getRange(cur,CodeMirror.Pos(cur.line, t, "\n"));
          }
          tend=cm.getRange(CodeMirror.Pos(cur.line, token.state.tagStart + token.state.tagName.length+1),CodeMirror.Pos(cur.line, t, "\n"));
          hasChild = tend.match(r2);
        }
        for (var attr in attrs) if (attrs.hasOwnProperty(attr) && (!prefix || matches(attr, prefix, matchInMiddle))){
          if(!hasChild || !hasChild.includes(attr)){
              result.push(attr);
          }
        }
      }
    }
    
    function returnHints() {
      return {
        list: result,
        from: replaceToken ? Pos(cur.line, tagStart == null ? token.start : tagStart) : cur,
        to: replaceToken ? Pos(cur.line, token.end) : cur
      };
    }
    return returnHints();
  }
  
  CodeMirror.registerHelper("hint", "mybatis", function(cm, options) {
    var local = {schemaInfo: data};
    if (options) for (var opt in options) local[opt] = options[opt];
    return getHints(cm, local);
  });
  
  
  function completeAfter(cm, pred) {
    var cur = cm.getCursor();
    if (!pred || pred()) setTimeout(function() {
      if (!cm.state.completionActive)
        cm.showHint({completeSingle: false});
    }, 100);
    return CodeMirror.Pass;
  }
  function completeIfAfterLt(cm) {
    return completeAfter(cm, function() {
      var cur = cm.getCursor();
      return cm.getRange(CodeMirror.Pos(cur.line, cur.ch - 1), cur) == "<";
    });
  }
  function completeIfInTag(cm) {
    return completeAfter(cm, function() {
      var tok = cm.getTokenAt(cm.getCursor());
      if (tok.type == "string" && (!new RegExp("['\"]").test(tok.string.charAt(tok.string.length - 1)) || tok.string.length == 1)) return false;
      var inner = CodeMirror.innerMode(cm.getMode(), tok.state).state;
      return inner.tagName;
    });
  }
  CodeMirror.commands.completeIfInTag = function(cm) {
        return completeIfInTag(cm);
  };
}